using System;
using System.Linq.Expressions;
using System.Reflection;

using TrueCode.Core.Aspects;
using TrueCode.DependencyManager.BuildActions.Injection;

namespace TrueCode.DependencyManager.BuildActions.Instantiating
{
  public class StaticMethodFactory : IInstantiate
  {
    private readonly string _paramterProviderKey;
    private readonly MethodInfo _method;
    private readonly ParameterInfo[] _parameters;

    public StaticMethodFactory( [NotNull] Type type, [NotNull] string methodName ) : this( type, methodName, null )
    {
    }

    public StaticMethodFactory( [NotNull] Type type, [NotNull] string methodName, [CanBeNull] string paramterProviderKey )
    {
      if( type == null ) throw new ArgumentNullException( "type" );
      if( methodName == null ) throw new ArgumentNullException( "methodName" );
      _paramterProviderKey = paramterProviderKey;
      _method = type.GetMethod( methodName, BindingFlags.Static | BindingFlags.Public );

      #region Assert that method is found
      if( _method == null )
      {
        var exception = new ArgumentException( "There is no specified method in type", "methodName" );
        exception.Data.Add( "Type", type );
        exception.Data.Add( "Method", methodName );
        throw exception;
      }
      #endregion

      _parameters = _method.GetParameters();
    }

    public void Execute( IBuildSession context )
    {
      var parameterValueProvider = context.GetService< IParameterProvider >( _paramterProviderKey );

      #region Assert that parameterValueProvider is not null
      if( parameterValueProvider == null )
      {
        var exception = new InvalidOperationException( "ParameterValueProvider is not registered." );
        exception.Data.Add( "Aim", context.SessionAim );
        throw exception;
      }
      #endregion

      try
      {
        context.Instance = _method.Invoke( null, parameterValueProvider.GetValuesFor( context, _parameters ) );
      }
      catch( Exception exc )
      {
        var exception = new Exception( exc.Message, exc );
        exception.Data.Add( "Aim", context.SessionAim );
        exception.Data.Add( "Method", _method );
        throw exception;
      }
      context.ExecuteNextBuildAction();
    }
  }

  public class StaticMethodFactory< T > : StaticMethodFactory
  {
    public StaticMethodFactory( [NotNull] string methodName ) : this( methodName, null )
    {
    }

    public StaticMethodFactory( [NotNull] string methodName, [CanBeNull] string paramterProviderKey )
      : base( typeof( T ), methodName, paramterProviderKey )
    {
    }

    //TOImplement
//    public StaticMethodFactory( Expression< Func< T > > exp ) : this( TypeSafety.GetNameOf( exp ) )
//    {
//      
//    }
  }
}